/*  Copyright (c) 2012 William Rogers and James Boud

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

using System;
using SlimDX;
using SlimDX.Direct3D11;
using Microsoft.Xna.Framework.Content;
using JBBRXG11ContentTexture;

namespace JBBRXG11
{
    public class Texture2D : ContentTypeReader<Texture2D>, IDisposable
    {
        protected SlimDX.Direct3D11.Texture2D dxtexture;
        ShaderResourceView dxtextureview;
        int width, height;
        bool this_wraps_an_external_texture;

        internal SlimDX.Direct3D11.Texture2D DXTexture { get { return dxtexture; } }
        internal ShaderResourceView View { get { return dxtextureview; } }

        public Microsoft.Xna.Framework.Rectangle Bounds { get { return new Microsoft.Xna.Framework.Rectangle(0, 0, width, height); } }
        public int Height { get { return height; } }
        public int Width { get { return width; } }

        private void ProcessInfo(GraphicsDevice device, Texture2DInfoStuff info)
        {
            // This routine constructs a texture2d from information read from the content pipeline
            Texture2DDescription desc;
            desc = new Texture2DDescription();
            desc.Width = info.mipchain[0].width;
            desc.Height = info.mipchain[0].height;
            if (info.mipchain[0].format != Microsoft.Xna.Framework.Graphics.SurfaceFormat.Color)
                throw new Exception("JBBRXG11(NYI): Texture2D can only cope with Color surface format");
            desc.Format = SlimDX.DXGI.Format.R8G8B8A8_UNorm;// tis.mipchain[0].format;  todo translate formats
            desc.MipLevels = info.mipchain.Length;
            desc.OptionFlags = 0;
            desc.BindFlags = BindFlags.ShaderResource;
            desc.CpuAccessFlags = CpuAccessFlags.None;
            desc.SampleDescription = new SlimDX.DXGI.SampleDescription(1, 0);
            desc.Usage = ResourceUsage.Immutable;
            desc.ArraySize = 1;

            DataRectangle[] data = new DataRectangle[info.mipchain.Length];
            for (int m = 0; m < info.mipchain.Length; m++)
            {
                data[m] = new DataRectangle(info.mipchain[m].width * 4,
                                            new DataStream(info.mipchain[m].data, true, false));
            }

            dxtexture = new SlimDX.Direct3D11.Texture2D(device.DX_Device, desc, data);
            dxtextureview = new ShaderResourceView(device.DX_Device, dxtexture);
            width = desc.Width;
            height = desc.Height;
        }

        protected override Texture2D Read(ContentReader input, Texture2D existingInstance)
        {
            // This is the content pipeline read routine
            Texture2DInfoStuff tis = new Texture2DInfoStuff();
            int chainlen, datalen;

            chainlen = input.ReadInt32();
            tis.mipchain = new TextureBitmap[chainlen];
            for (int m = 0; m < chainlen; m++)
            {
                tis.mipchain[m].width = input.ReadInt32();
                tis.mipchain[m].height = input.ReadInt32();
                int format = input.ReadInt32();
                tis.mipchain[m].format = (Microsoft.Xna.Framework.Graphics.SurfaceFormat)(format);
                datalen = input.ReadInt32();
                tis.mipchain[m].data = input.ReadBytes(datalen);
            }

            // Can't use this instance as it belongs to the content manager
            // The content manager may reuse it, thus overwriting this object
            Texture2D newtex = new Texture2D();
            newtex.ProcessInfo(Game.GraphicsDevice, tis);
            GraphicsDeviceManager.GetGame(Game.GraphicsDevice).UserContentAdd(newtex);
            newtex.this_wraps_an_external_texture = false;
            return newtex;
        }

        /// <summary>
        /// Do not use this constructor, required by the content system
        /// </summary>
        public Texture2D() // Constructor for content system to use to before calling Read - not in XNA API
        {
            dxtexture = null;
            dxtextureview = null;
            this_wraps_an_external_texture = false;  // TODO: need to find out if this disposes correctly or at all
        }

        internal Texture2D(SlimDX.Direct3D11.Texture2D wrapped,  // Wrap an existing texture
                           SlimDX.Direct3D11.ShaderResourceView wrappedview,
                           int w, int h)
        {
            dxtexture = wrapped;
            dxtextureview = wrappedview;
            width = w;
            height = h;
            GraphicsDeviceManager.GetGame(Game.GraphicsDevice).UserContentAdd(this);
            this_wraps_an_external_texture = true;
        }

        void MakeTheTexture(GraphicsDevice graphicsDevice, int width, int height, bool mipMap, Microsoft.Xna.Framework.Graphics.SurfaceFormat format, bool couldberendertarget)
        {
            Texture2DDescription desc;
            desc = new Texture2DDescription();
            desc.Width = width;
            desc.Height = height;
            if (format != Microsoft.Xna.Framework.Graphics.SurfaceFormat.Color)
            {
                throw new Exception("DXna - use of formats other than colour not yet implemented");
            }
            desc.Format = SlimDX.DXGI.Format.R8G8B8A8_UNorm;
            desc.MipLevels = (mipMap ? 0 : 1);
            desc.OptionFlags = 0;
            if (couldberendertarget)
                desc.BindFlags = BindFlags.ShaderResource | BindFlags.RenderTarget;
            else
                desc.BindFlags = BindFlags.ShaderResource;
            desc.CpuAccessFlags = CpuAccessFlags.None;
            desc.SampleDescription = new SlimDX.DXGI.SampleDescription(1, 0);
            desc.Usage = ResourceUsage.Default;
            desc.ArraySize = 1;

            dxtexture = new SlimDX.Direct3D11.Texture2D(graphicsDevice.DX_Device, desc);
            dxtextureview = new ShaderResourceView(graphicsDevice.DX_Device, dxtexture);

            this.width = width;
            this.height = height;
            this_wraps_an_external_texture = false;
        }

        /// <summary>
        /// Creates a new Texture2D instance of specified size, no mipmap levels
        /// </summary>
        /// <param name="graphicsDevice">The graphics device</param>
        /// <param name="width">Required texture width (pixels)</param>
        /// <param name="height">Required texture height (pixels)</param>
        public Texture2D(GraphicsDevice graphicsDevice, int width, int height)
        {
            //Texture2DDescription desc;
            //desc = new Texture2DDescription();
            //desc.Width = width;
            //desc.Height = height;
            //desc.Format = SlimDX.DXGI.Format.R8G8B8A8_UNorm;
            //desc.MipLevels = 1;
            //desc.OptionFlags = 0;
            //desc.BindFlags = BindFlags.ShaderResource;  // TODO: should this include RenderTarget?
            //desc.CpuAccessFlags = CpuAccessFlags.None;
            //desc.SampleDescription = new SlimDX.DXGI.SampleDescription(1, 0);
            //desc.Usage = ResourceUsage.Default;
            //desc.ArraySize = 1;

            //dxtexture = new SlimDX.Direct3D11.Texture2D(graphicsDevice.DX_Device, desc);
            //dxtextureview = new ShaderResourceView(graphicsDevice.DX_Device, dxtexture);

            //this.width = width;
            //this.height = height;
            //this_wraps_an_external_texture = false;
            MakeTheTexture(graphicsDevice, width, height, false, Microsoft.Xna.Framework.Graphics.SurfaceFormat.Color, false);
            GraphicsDeviceManager.GetGame(Game.GraphicsDevice).UserContentAdd(this);
        }

        /// <summary>
        /// Creates a new instance of this object
        /// </summary>
        /// <param name="graphicsDevice">The graphics device</param>
        /// <param name="width">Required texture width(pixels)</param>
        /// <param name="height">Required texture height(pixels)</param>
        /// <param name="mipMap">[MarshalAsAttribute(U1)] True to generate a full mipmap chain; false otherwise</param>
        /// <param name="format">Required texture data format</param>
        public Texture2D(GraphicsDevice graphicsDevice, int width, int height, bool mipMap, Microsoft.Xna.Framework.Graphics.SurfaceFormat format)
        {
            MakeTheTexture(graphicsDevice, width, height, mipMap, format, false);
            GraphicsDeviceManager.GetGame(Game.GraphicsDevice).UserContentAdd(this);
        }

        /// <summary>
        /// This is for internal use only - by the subclass RenderTarget2D
        /// </summary>
        protected Texture2D(GraphicsDevice graphicsDevice, int width, int height, bool mipMap, Microsoft.Xna.Framework.Graphics.SurfaceFormat format, bool forrendertarget)
        {
            MakeTheTexture(graphicsDevice, width, height, mipMap, format, forrendertarget);
            // The resource is registered for disposal by the subclass
        }

        internal static void CalculateLevelSize(int level, int texwidth, int texheight, out int levelwidth, out int levelheight)
        {
            int lev = level, wide = texwidth, high = texheight;
            while (lev > 0)
            {
                if (wide > 1)
                {
                    wide /= 2;
                    high /= 2; if (high < 1) high = 1;
                    lev--;
                }
                else if (high > 1)
                {
                    high /= 2;
                    wide /= 2; if (wide < 1) wide = 1;
                    lev--;
                }
                else
                {
                    throw new Exception("DXna: attempt to set texture data to mip level beyond maximum(" + level.ToString() + " >= " + (level - lev).ToString() + ")");
                }
            }
            levelwidth = wide;
            levelheight = high;
        }

        /// <summary>
        /// Sets 2D texture data. Reference page contains links to related conceptual articles
        /// </summary>
        /// <typeparam name="T">The type of the elements in the array</typeparam>
        /// <param name="data">Array of data</param>
        public void SetData<T>(T[] data) where T : struct
        {
            SetData<T>(data, 0, width * height);
        }

        /// <summary>
        /// Sets 2D texture data, specifying a start index, and number of elements
        /// </summary>
        /// <typeparam name="T">Element type</typeparam>
        /// <param name="data">Array of data</param>
        /// <param name="startIndex">Index of the first element to set</param>
        /// <param name="elementCount">Number of elements to set</param>
        public void SetData<T>(T[] data, int startIndex, int elementCount) where T : struct
        {
            if (elementCount != width * height)
                throw new Exception("Data array is not the right size for texture data");
            if (startIndex < 0 || startIndex + elementCount > data.Length)
                throw new Exception("Index range does not lie within the data array");
            
            // Create a staging buffer to get CPU write access
            Texture2DDescription desc;
            SlimDX.Direct3D11.Texture2D dxstaging;
            desc = dxtexture.Description;
            desc.BindFlags = BindFlags.None;
            desc.CpuAccessFlags = CpuAccessFlags.Write;
            desc.Usage = ResourceUsage.Staging;
            dxstaging = new SlimDX.Direct3D11.Texture2D(Game.GraphicsDevice.DX_Device, desc);

            // Write data to staging buffer
            DataBox box = Game.GraphicsDevice.DX_Device_Context.MapSubresource(dxstaging, 0, 0, MapMode.Write, MapFlags.None);
            box.Data.WriteRange<T>(data, startIndex, elementCount);
            Game.GraphicsDevice.DX_Device_Context.UnmapSubresource(dxstaging, 0);

            // Transfer data to the texture2D
            Game.GraphicsDevice.DX_Device_Context.CopyResource(dxstaging, dxtexture);

            // Dispose of the unmanaged staging buffer structure
            dxstaging.Dispose();
        }

        /// <summary>
        /// Sets 2D texture data, specifying a mipmap level, source rectangle, start index, and number of elements
        /// </summary>
        /// <typeparam name="T">Element type</typeparam>
        /// <param name="level">Mipmap level</param>
        /// <param name="rect">A bounding box that defines the position and location (in pixels) of the data</param>
        /// <param name="data">An array of element data</param>
        /// <param name="startIndex">Index of the first element to set</param>
        /// <param name="elementCount">Number of elements to set</param>
        public void SetData<T>(int level, Microsoft.Xna.Framework.Rectangle? rect, T[] data, int startIndex, int elementCount) where T : struct
        {
            // Create a staging buffer to get CPU write access
            Texture2DDescription desc;
            int left = 0, top = 0, wide, high;
            SlimDX.Direct3D11.Texture2D dxstaging;
            desc = dxtexture.Description;
            CalculateLevelSize(level, width, height, out wide, out high);
            if (rect.HasValue)
            {
                left = rect.Value.Left;
                top = rect.Value.Top;
                if (left < 0 || left >= wide || top < 0 || top >= high || rect.Value.Width > wide || rect.Value.Height > high)
                    throw new Exception("Rectangle (" + top.ToString() + "," + left.ToString() + "," + rect.Value.Right.ToString() + "," + rect.Value.Bottom.ToString()
                                        + ") not inside mip level " + level.ToString() + " of Texture2D(" + wide.ToString() + "," + high.ToString() + ")");
                wide = rect.Value.Width;
                high = rect.Value.Height;
            }
            if (elementCount != wide * high)
                throw new Exception("Data array is not the right size for texture data");
            if (startIndex < 0 || startIndex + elementCount > data.Length)
                throw new Exception("Index range does not lie within the data array");
            desc.Width = wide;
            desc.Height = high;
            desc.MipLevels = 1;
            desc.BindFlags = BindFlags.None;
            desc.CpuAccessFlags = CpuAccessFlags.Write;
            desc.Usage = ResourceUsage.Staging;
            dxstaging = new SlimDX.Direct3D11.Texture2D(Game.GraphicsDevice.DX_Device, desc);

            // Write data to staging buffer  TODO - have noticed that for 3D textures the RowPitch of the staging
            // buffer may be larger than expected.  This needs checking for this (Texture2D) case TODO TODO
            DataBox box = Game.GraphicsDevice.DX_Device_Context.MapSubresource(dxstaging, 0, 0, MapMode.Write, MapFlags.None);
            box.Data.WriteRange<T>(data, startIndex, elementCount);
            Game.GraphicsDevice.DX_Device_Context.UnmapSubresource(dxstaging, 0);

            // Transfer data to the texture2D
            Game.GraphicsDevice.DX_Device_Context.CopySubresourceRegion(dxstaging, 0,
                                                                        new ResourceRegion(0, 0, 0, wide, high, 1),
                                                                        dxtexture, level, left, top, 0);

            // Dispose of the unmanaged staging buffer structure
            dxstaging.Dispose();
        }

        /// <summary>
        /// Gets a copy of 2D texture data. Reference page contains code sample
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="data">Array for data</param>
        public void GetData<T>(T[] data) where T : struct
        {
            GetData<T>(data, 0, width * height);
            //// Create a staging buffer to get CPU read access
            //Texture2DDescription desc;
            //SlimDX.Direct3D11.Texture2D dxstaging;
            //desc = dxtexture.Description;
            //desc.MipLevels = 1;
            //desc.BindFlags = BindFlags.None;
            //desc.CpuAccessFlags = CpuAccessFlags.Read;
            //desc.Usage = ResourceUsage.Staging;
            //dxstaging = new SlimDX.Direct3D11.Texture2D(Game.GraphicsDevice.DX_Device, desc);

            //// Transfer data from the texture2d to the staging buffer
            //// Note - CopyResource seems to require resources of exactly same size and depth, so we
            //// must use CopySubresourceRegion to copy into the single mip level of the staging buffer
            //Game.GraphicsDevice.DX_Device_Context.CopySubresourceRegion(dxtexture, 0, new ResourceRegion(0, 0, 0, desc.Width, desc.Height, 1), dxstaging, 0, 0, 0, 0);

            //// Read data from staging buffer
            //DataBox box = Game.GraphicsDevice.DX_Device_Context.MapSubresource(dxstaging, 0, 0, MapMode.Read, MapFlags.None);
            //box.Data.ReadRange<T>(data, 0, width * height);
            //Game.GraphicsDevice.DX_Device_Context.UnmapSubresource(dxstaging, 0);

            //// Dispose of the unmanaged staging buffer structure
            //dxstaging.Dispose();
        }

        /// <summary>
        /// Gets a copy of 2D texture data, specifying a start index and number of elements
        /// </summary>
        /// <typeparam name="T">Element type</typeparam>
        /// <param name="data">Array of element data</param>
        /// <param name="startIndex">Index of the first element to get</param>
        /// <param name="elementCount">Number of elements to get</param>
        public void GetData<T>(T[] data, int startIndex, int elementCount) where T : struct
        {
            // Assuming here that the data comes from the top mip level
            if (elementCount != width * height)
                throw new Exception("Data array is not the right size for texture data");
            if (startIndex < 0 || startIndex + elementCount > data.Length)
                throw new Exception("Index range does not lie within the data array");

            // Create a staging buffer to get CPU read access
            Texture2DDescription desc;
            SlimDX.Direct3D11.Texture2D dxstaging;
            desc = dxtexture.Description;
            desc.BindFlags = BindFlags.None;
            desc.CpuAccessFlags = CpuAccessFlags.Read;
            desc.Usage = ResourceUsage.Staging;
            // TODO: Assumes that T is Color - but don't know how to check that.  Should be ok though
            // as I don't provide any way of creating textures of any other element kind.
            desc.MipLevels = 1;
            dxstaging = new SlimDX.Direct3D11.Texture2D(Game.GraphicsDevice.DX_Device, desc);

            // Transfer data from the texture2d to the staging buffer
            // Note - CopyResource seems to require resources of exactly same size and depth, so we
            // must use CopySubresourceRegion to copy into the single mip level of the staging buffer
            Game.GraphicsDevice.DX_Device_Context.CopySubresourceRegion(dxtexture, 0,
                                                                        new ResourceRegion(0, 0, 0, desc.Width, desc.Height, 1),
                                                                        dxstaging, 0, 0, 0, 0);

            // Read data from staging buffer
            DataBox box = Game.GraphicsDevice.DX_Device_Context.MapSubresource(dxstaging, 0, 0, MapMode.Read, MapFlags.None);
            box.Data.ReadRange<T>(data, startIndex, elementCount);
            Game.GraphicsDevice.DX_Device_Context.UnmapSubresource(dxstaging, 0);

            // Dispose of the unmanaged staging buffer structure
            dxstaging.Dispose();
        }

        /// <summary>
        /// Gets a copy of 2D texture data, specifying a mipmap level, source rectangle, start index, and number of elements
        /// </summary>
        /// <typeparam name="T">Element type</typeparam>
        /// <param name="level">Mipmap level</param>
        /// <param name="rect">The section of the texture to copy.  null indicates the data will be copied from the entire texture</param>
        /// <param name="data">Array of element data</param>
        /// <param name="startIndex">Index of the first element to get</param>
        /// <param name="elementCount">Number of elements to get</param>
        public void GetData<T>(int level, Microsoft.Xna.Framework.Rectangle? rect, T[] data, int startIndex, int elementCount) where T : struct
        {
            // Create a staging buffer to get CPU read access
            Texture2DDescription desc;
            int left = 0, top = 0, wide, high;
            SlimDX.Direct3D11.Texture2D dxstaging;
            desc = dxtexture.Description;
            CalculateLevelSize(level, width, height, out wide, out high);
            if (rect.HasValue)
            {
                left = rect.Value.Left;
                top = rect.Value.Top;
                if (left < 0 || left >= wide || top < 0 || top >= high || rect.Value.Width > wide || rect.Value.Height > high)
                    throw new Exception("Rectangle (" + top.ToString() + "," + left.ToString() + "," + rect.Value.Right.ToString() + "," + rect.Value.Bottom.ToString()
                                        + ") not inside mip level " + level.ToString() + " of Texture2D(" + wide.ToString() + "," + high.ToString() + ")");
                wide = rect.Value.Width;
                high = rect.Value.Height;
            }
            if (elementCount != wide * high)
                throw new Exception("Data array is not the right size for texture data");
            if (startIndex < 0 || startIndex + elementCount > data.Length)
                throw new Exception("Index range does not lie within the data array");
            desc.Width = wide;
            desc.Height = high;
            desc.MipLevels = 1;
            desc.BindFlags = BindFlags.None;
            desc.CpuAccessFlags = CpuAccessFlags.Read;
            desc.Usage = ResourceUsage.Staging;
            // TODO: Assumes that T is Color - but don't know how to check that.  Should be ok though
            // as I don't provide any way of creating textures of any other element kind.
            dxstaging = new SlimDX.Direct3D11.Texture2D(Game.GraphicsDevice.DX_Device, desc);

            // Transfer data from the texture2d to the staging buffer
            // Note - CopyResource seems to require resources of exactly same size and depth, so we
            // must use CopySubresourceRegion to copy into the single mip level of the staging buffer
            Game.GraphicsDevice.DX_Device_Context.CopySubresourceRegion(dxtexture, 0,
                                                                        new ResourceRegion(left, top, level, left + wide, top + high, level + 1),
                                                                        dxstaging, 0, 0, 0, 0);

            // Read data from staging buffer
            DataBox box = Game.GraphicsDevice.DX_Device_Context.MapSubresource(dxstaging, 0, 0, MapMode.Read, MapFlags.None);
            box.Data.ReadRange<T>(data, startIndex, elementCount);
            Game.GraphicsDevice.DX_Device_Context.UnmapSubresource(dxstaging, 0);

            // Dispose of the unmanaged staging buffer structure
            dxstaging.Dispose();
        }

        /// <summary>
        /// Write texture to any of a range of file types (DXna extension to XNA)
        /// </summary>
        /// <param name="device">Graphics Device</param>
        /// <param name="filename">Name of file to write</param>
        /// <param name="format">Image file format to use</param>
        public void Save(GraphicsDevice device, string filename, ImageFileFormat format)
        {
            SlimDX.Direct3D11.Texture2D.ToFile(device.DX_Device_Context, dxtexture, format, filename);
        }

        /// <summary>
        /// Saves texture data as a .jpg
        /// </summary>
        /// <param name="stream">Data stream number</param>
        /// <param name="width">Image width</param>
        /// <param name="height">Image height</param>
        public void SaveAsJpeg(System.IO.Stream stream, int width, int height)
        {
            SlimDX.Direct3D11.Texture2D.ToStream(Game.GraphicsDevice.DX_Device_Context, dxtexture, ImageFileFormat.Jpg, stream);
        }

        /// <summary>
        /// Saves texture data as a .png
        /// </summary>
        /// <param name="stream">Data stream number</param>
        /// <param name="width">Image width</param>
        /// <param name="height">Image height</param>
        public void SaveAsPng(System.IO.Stream stream, int width, int height)
        {
            SlimDX.Direct3D11.Texture2D.ToStream(Game.GraphicsDevice.DX_Device_Context, dxtexture, ImageFileFormat.Png, stream);
        }

        /// <summary>
        /// Loads texture data from a stream, makes mip levels (does not close stream after reading)
        /// </summary>
        /// <param name="graphicsDevice">A graphics device</param>
        /// <param name="stream">Data stream from one of the following file types: .gif, .jpg or .png</param>
        /// <returns></returns>
        public static Texture2D FromStream(GraphicsDevice graphicsDevice, System.IO.Stream stream)
        {
            Texture2D newtex = new Texture2D();
            newtex.dxtexture = SlimDX.Direct3D11.Texture2D.FromStream(graphicsDevice.DX_Device, stream, (int)stream.Length);
            newtex.dxtextureview = new ShaderResourceView(graphicsDevice.DX_Device, newtex.dxtexture);
            newtex.this_wraps_an_external_texture = false;
            newtex.width = newtex.dxtexture.Description.Width;
            newtex.height = newtex.dxtexture.Description.Height;
            GraphicsDeviceManager.GetGame(Game.GraphicsDevice).UserContentAdd(newtex);
            return newtex;
        }

        /// <summary>
        /// Loads and resizes texture data from a stream, does not make mip levels (does not close stream) 
        /// </summary>
        /// <param name="graphicsDevice">A graphics device</param>
        /// <param name="stream">Data stream from one of the following file types: .gif, .jpg or .png</param>
        /// <param name="width">The requested image width</param>
        /// <param name="height">The requested image height</param>
        /// <param name="zoom">Control the aspect ratio when zooming (scaling); set to false to maintain
        ///     a constant aspect ratio, true otherwise. See remarks</param>
        /// <returns></returns>
        public static Texture2D FromStream(GraphicsDevice graphicsDevice, System.IO.Stream stream, int width, int height, bool zoom)
        {
            // There is probably a better way of doing this, but to check for correct aspect ratio, this code
            // loads the texture once to measure its size, then loads it again in the requested size (adjusted
            // to preserve aspect ratio if necessary)
            if (!zoom)
            {
                long streamposition = stream.Position;
                SlimDX.Direct3D11.Texture2D tempdxtexture = SlimDX.Direct3D11.Texture2D.FromStream(graphicsDevice.DX_Device, stream, (int)stream.Length);
                int actualwidth = tempdxtexture.Description.Width, actualheight = tempdxtexture.Description.Height;
                tempdxtexture.Dispose();
                stream.Position = streamposition;

                if (width * actualheight != height * actualwidth)
                {
                    // Correct width and height for aspect ratio
                    float widening = (float)width / (float)actualwidth;
                    float heightening = (float)height / (float)actualheight;
                    if (widening < heightening)
                        height = (int)(actualheight * widening + 0.5f);
                    else
                        width = (int)(actualwidth * heightening + 0.5f);
                }
            }

            Texture2D newtex = new Texture2D();
            ImageLoadInformation ili = new ImageLoadInformation();
            ili.BindFlags = BindFlags.ShaderResource;
            ili.CpuAccessFlags = CpuAccessFlags.None;
            ili.Depth = 1;
            ili.FilterFlags = FilterFlags.Linear;
            ili.FirstMipLevel = 0;
            ili.Format = SlimDX.DXGI.Format.R8G8B8A8_UNorm;
            ili.Height = height;
            ili.MipFilterFlags = FilterFlags.Linear;
            ili.MipLevels = 1;
            ili.OptionFlags = ResourceOptionFlags.None;
            ili.Usage = ResourceUsage.Default;
            ili.Width = width;
            newtex.dxtexture = SlimDX.Direct3D11.Texture2D.FromStream(graphicsDevice.DX_Device, stream, (int)stream.Length, ili);
            newtex.dxtextureview = new ShaderResourceView(graphicsDevice.DX_Device, newtex.dxtexture);
            newtex.this_wraps_an_external_texture = false;
            newtex.width = newtex.dxtexture.Description.Width;
            newtex.height = newtex.dxtexture.Description.Height;
            GraphicsDeviceManager.GetGame(Game.GraphicsDevice).UserContentAdd(newtex);
            return newtex;
        }

        public void Dispose()
        {
            if (!this_wraps_an_external_texture)
            {
                dxtextureview.Dispose();
                dxtexture.Dispose();
            }
        }
    }
}

// All of the XNA 4 API is implemented

/*
 * TODO:  Check that ImageFileFormat is not in XNA and import for write to file
 * TODO:  Notes - the SetData routine(s) create a staging buffer - as I understand the documentation that should have been necessary only for GetData
 * TODO:  Have patched the SpriteBatch implementation for magnifying images - need to back port this into the main version and find out why the error
 *        wasn't found in the SpriteBatch tests
 * */
